package cn.com.pagoda.ideplugin.service;

import cn.com.pagoda.ideplugin.common.StringUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import java.util.Iterator;
import java.util.Map;

/**
 * @author tianmingxing <mx.tian@qq.com> on 2023-05-06
 */
public class JsonEditorService {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final AnActionEvent anActionEvent;
    private final JPanel panel;

    public JsonEditorService(AnActionEvent anActionEvent, JPanel panel) {
        this.anActionEvent = anActionEvent;
        this.panel = panel;
    }

    /**
     * 尝试将用户输入的文本转换成JSON
     *
     * @param jsonText 用户输入的文本
     * @param pretty   true表示需要美化格式，反之格式是压缩的
     * @return
     */
    public String jsonHandler(String jsonText, boolean pretty) {
        // 从文本框中提取数据
        if (StringUtils.isBlank(jsonText)) {
            return "{ }";
        }

        // 转换成JSON对象
        JsonNode jsonNode;
        try {
            jsonNode = OBJECT_MAPPER.readTree(jsonText);
        } catch (JsonProcessingException ex) {
            var error = "JSON解析异常：" + ex.getMessage();
            JOptionPane.showMessageDialog(panel, error);
            return null;
        }

        // 格式化成多选字符串
        return pretty ? jsonNode.toPrettyString() : jsonNode.toString();
    }

    /**
     * 将JSON转换成类，并将类的源码输出。
     * <p>
     * 1. 对JSON再次校验
     * 2. 从JsonNode获取类的属性
     * 3. 填充模板
     *
     * @param jsonText 已经被格式化后的文本，但不排除用户再次进行了修改。
     * @return 类的源码
     */
    public String toClassHandler(String jsonText) {
        JsonNode jsonNode;
        try {
            jsonNode = OBJECT_MAPPER.readTree(jsonText);
        } catch (JsonProcessingException ex) {
            var error = "JSON解析异常：" + ex.getMessage();
            JOptionPane.showMessageDialog(panel, error);
            return null;
        }

        var buf = new StringBuffer();
        buf.append("import java.util.List;\n")
                .append("import lombok.Getter;\n")
                .append("import lombok.Setter;\n")
                .append("import lombok.ToString;\n\n")
                .append("@Getter\n").append("@Setter\n").append("@ToString\n")
                .append("public class DTO {\n\n");
        parseJsonNode(buf, jsonNode, "    ");
        buf.append("\n}\n");
        return buf.toString();
    }

    private static void parseJsonNode(StringBuffer buf, JsonNode jsonNode, String indent) {
        if (jsonNode.isObject()) {
            Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> entry = fields.next();
                String propertyName = entry.getKey();
                JsonNode propertyValue = entry.getValue();

                if (propertyValue.isArray()) {
                    var javaTypeStr = getArrayType(propertyValue);
                    if ("Object".equals(javaTypeStr)) {
                        var className = StringUtil.convertToCamelCase(propertyName);
                        buf.append(indent)
                                .append("public List<")
                                .append(className).append("> ")
                                .append(propertyName).append(";\n")
                                .append(indent).append("@Getter\n")
                                .append(indent).append("@Setter\n")
                                .append(indent).append("@ToString\n")
                                .append(indent).append("public static class ")
                                .append(className).append(" {\n");
                        parseJsonNode(buf, propertyValue.get(0), indent + "    ");
                        buf.append(indent).append("}\n");
                    } else {
                        buf.append(indent)
                                .append("public List<")
                                .append(javaTypeStr).append("> ")
                                .append(propertyName).append(";\n");
                    }
                } else if (propertyValue.isObject()) {
                    var className = StringUtil.convertToCamelCase(propertyName);
                    buf.append(indent).append("public ")
                            .append(className)
                            .append(" ").append(propertyName).append(";\n")
                            .append(indent).append("@Getter\n")
                            .append(indent).append("@Setter\n")
                            .append(indent).append("@ToString\n")
                            .append(indent).append("public static class ")
                            .append(className).append(" {\n");
                    parseJsonNode(buf, propertyValue, indent + "    ");
                    buf.append(indent).append("}\n");
                } else {
                    String propertyType = getPropertyType(propertyValue);
                    buf.append(indent).append("public ")
                            .append(propertyType).append(" ")
                            .append(propertyName).append(";\n");
                }
            }
        }
    }

    private static String getPropertyType(JsonNode jsonNode) {
        if (jsonNode.isTextual()) {
            return "String";
        } else if (jsonNode.isBoolean()) {
            return "Boolean";
        } else if (jsonNode.isInt()) {
            return "Integer";
        } else if (jsonNode.isLong()) {
            return "Long";
        } else if (jsonNode.isDouble()) {
            return "Double";
        } else if (jsonNode.isArray()) {
            return "List";
        } else if (jsonNode.isObject()) {
            return jsonNode.getNodeType().name();
        } else {
            return "Object";
        }
    }

    private static String getArrayType(JsonNode jsonNode) {
        JsonNode firstElement = jsonNode.get(0);
        if (firstElement.isInt()) {
            return "Integer";
        } else if (firstElement.isLong()) {
            return "Long";
        } else if (firstElement.isDouble()) {
            return "Double";
        } else if (firstElement.isTextual()) {
            return "String";
        } else if (firstElement.isBoolean()) {
            return "Boolean";
        } else if (firstElement.isArray()) {
            return "List";
        } else if (firstElement.isObject()) {
            return "Object";
        }
        return "None";
    }

}
